﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using H2.Streams;

namespace Mystery.SCNR.Scripts
{
    public class ScriptingSyntaxDatum
    {
        public const int Offset = 568;
        public const int ChunkSize = 20;

        public bool Keep = true;

        public ushort ConstantType;
        public BuiltInFunctions OwnerFunction;
        public VariableTypes Type;
        public DatumFlags Flags;
        public ushort SiblingIndex;
        public ushort SiblingIndexSalt;
        public uint Pointer;
        public byte[] Data;

        public uint Index;
        public ScriptingSyntaxDatum Previous;
        public ScriptingSyntaxDatum Sibling;
        public ScriptingSyntaxDatum Child;

        public bool HasChild()
        {
            return Child != null;
        }

        public UInt16 GetChildIndex()
        {
            UInt16 index = 0;
            switch (Type)
            {
                case VariableTypes.Real:
                case VariableTypes.Boolean:
                case VariableTypes.String:
                    index = BitConverter.ToUInt16(Data, (Flags == DatumFlags.ConcatenatedPrimitive || Flags == DatumFlags.ConcatenatedVariablePrimitive) ? 2 : 0);
                    break;
                default:
                    index = BitConverter.ToUInt16(Data, 0);
                    break;
            }

            if (index == 0xFFFF || index == 0xBABA) return index;
            return (UInt16)(index & 0x7FFF);
        }

        public bool HasSibling()
        { return Sibling != null; }

        public ScriptingSyntaxDatum GetSibling(ScriptingSyntaxDatum[] datums)
        {
            return datums[SiblingIndex];
        }

        public ScriptingSyntaxDatum GetChild(ScriptingSyntaxDatum[] datums)
        {
            return datums[GetChildIndex()];
        }

        public void SetChild(ScriptingSyntaxDatum[] datums, ushort index)
        {
            byte[] bytes = BitConverter.GetBytes(index);
            Data[0] = bytes[0];
            Data[1] = bytes[1];
            if (index == 0xFFFF)
            {
                Child = null;
            }
            else
            {
                Child = datums[index];
                Child.Previous = this;
            }
        }

        public void SetSibling(ScriptingSyntaxDatum[] datums, ushort index, ushort indexSalt)
        {
            SiblingIndex = index;
            SiblingIndexSalt = indexSalt;
            if (index == 0xFFFF)
            {
                Sibling = null;
            }
            else
            {
                Sibling = datums[index];
                Sibling.Previous = this;
            }
        }

        public string GetStartString()
        {
            if (Type == VariableTypes.FunctionName)
            {
                switch (OwnerFunction)
                {
                    case BuiltInFunctions.Begin:
                        return string.Empty;
                    case BuiltInFunctions.And:
                    case BuiltInFunctions.EqualTo:
                    case BuiltInFunctions.NotEqualTo:
                    case BuiltInFunctions.Or:
                    case BuiltInFunctions.GreaterThan:
                    case BuiltInFunctions.LessThan:
                    case BuiltInFunctions.GreaterThanOrEqualTo:
                    case BuiltInFunctions.LessThanOrEqualTo:
                    case BuiltInFunctions.Add:
                    case BuiltInFunctions.Minus:
                    case BuiltInFunctions.Multiply:
                    case BuiltInFunctions.Divide:
                        return string.Empty;
                }
            }
            return GetValue().ToString();
        }

        public string GetSeparatorString()
        {
            switch (OwnerFunction)
            {
                case BuiltInFunctions.Begin: return "\r\n";
                case BuiltInFunctions.And: return " and ";
                case BuiltInFunctions.EqualTo: return " == ";
                case BuiltInFunctions.NotEqualTo: return " != ";
                case BuiltInFunctions.Or: return " or ";
                case BuiltInFunctions.GreaterThan: return " > ";
                case BuiltInFunctions.LessThan: return " < ";
                case BuiltInFunctions.GreaterThanOrEqualTo: return " >= ";
                case BuiltInFunctions.LessThanOrEqualTo: return " <= ";
                case BuiltInFunctions.If: return ") ";
                case BuiltInFunctions.Add: return " + ";
                case BuiltInFunctions.Minus: return " - ";
                case BuiltInFunctions.Multiply: return " * ";
                case BuiltInFunctions.Divide: return " / ";
                default: return ", ";
            }
        }

        public string GetEndString()
        {
            if (OwnerFunction == BuiltInFunctions.Begin) return string.Empty;
            if (HasChild() && Child.Type == VariableTypes.FunctionName)
                return ")";
            //switch (Type)
            //{
            //    case VariableTypes.Void:
            //    case VariableTypes.Script:
            //        return ")";
            //}
            return string.Empty;
        }

        public object GetValue()
        {
            if ((Flags & DatumFlags.Primitive) == 0) return string.Empty;

            switch (Type)
            {
                case VariableTypes.FunctionName: return Script.Current.GetString(Pointer) + '(';

                case VariableTypes.ObjectName:
                case VariableTypes.UnitName:
                case VariableTypes.VehicleName:
                case VariableTypes.WeaponName:
                case VariableTypes.DeviceName:
                case VariableTypes.SceneryName: return Script.Current.GetString(Pointer);

                case VariableTypes.Boolean: return BitConverter.ToBoolean(Data, 0);
                case VariableTypes.Real: return BitConverter.ToSingle(Data, 0);
                case VariableTypes.Short: return BitConverter.ToInt16(Data, 0);
                case VariableTypes.Long: return BitConverter.ToInt32(Data, 0);
                case VariableTypes.String:
                case VariableTypes.Team: return Script.Current.GetString(Pointer);
                case VariableTypes.Script: return Script.Current.Functions[BitConverter.ToUInt16(Data, 0)].GetName() + '(';
                //case VariableTypes.StringID: return BitConverter.ToInt32(Data, 0);
                //case VariableTypes.UnitSeatMapping: return BitConverter.ToSingle(Data, 0);
                //case VariableTypes.TriggerVolume: return BitConverter.ToSingle(Data, 0);
            }

            return Type.ToString() + "[" + Script.Current.GetString(Pointer) + "]";
        }

        public override string ToString()
        {
            return Type.ToString() + " - " + GetValue() + " - " + ConstantType.ToString() + " - " + Flags.ToString();
        }

        UInt16 childIndexDEBUG;
        UInt16 short0DEBUG;
        UInt16 short1DEBUG;

        public static ScriptingSyntaxDatum[] Read(MapStream map, int scnrOffset)
        {
            ScriptingSyntaxDatum[] scriptingSyntaxDatums = new ScriptingSyntaxDatum[map.ReadReflexiveAt(scnrOffset + Offset)];

            for (int i = 0; i < scriptingSyntaxDatums.Length; i++)
            {
                ScriptingSyntaxDatum ssd = new ScriptingSyntaxDatum();
                scriptingSyntaxDatums[i] = ssd;

                ssd.ConstantType = map.ReadUInt16();
                ssd.OwnerFunction = (BuiltInFunctions)map.ReadUInt16();
                ssd.Type = (VariableTypes)map.ReadUInt16();
                ssd.Flags = (DatumFlags)map.ReadUInt16();
                ssd.SiblingIndex = map.ReadUInt16();
                ssd.SiblingIndexSalt = map.ReadUInt16();
                ssd.Pointer = map.ReadUInt32();
                ssd.Data = map.ReadBytes(4);
            }

            //List<ScriptingSyntaxDatum> error = new List<ScriptingSyntaxDatum>();
            for (uint i = 0; i < scriptingSyntaxDatums.Length; i++)
            {
                ScriptingSyntaxDatum datum = scriptingSyntaxDatums[i];

                datum.Index = i;

                datum.short0DEBUG = BitConverter.ToUInt16(datum.Data, 0);
                datum.short1DEBUG = BitConverter.ToUInt16(datum.Data, 2);

                datum.childIndexDEBUG = datum.GetChildIndex();

                if (datum.Flags == DatumFlags.Concatenated || datum.Flags == DatumFlags.ConcatenatedReturnFuncValue) //Has Child Index
                {
                    uint childIndex = datum.GetChildIndex();
                    //if (childIndex >= scriptingSyntaxDatums.Length)
                    //{
                    //    if (childIndex != 0xFFFF) error.Add(datum);
                    //}
                        
                    //if (childIndex != 0xFFFF)
                    //{
                        ScriptingSyntaxDatum child = scriptingSyntaxDatums[childIndex];
                        child.Previous = datum;
                        datum.Child = child;
                    //}
                }
                if (datum.SiblingIndex != 0xBABA && datum.SiblingIndex != 0xFFFF) // Has Sibling
                {
                    ScriptingSyntaxDatum sibling = scriptingSyntaxDatums[datum.SiblingIndex];
                    sibling.Previous = datum;
                    datum.Sibling = sibling;
                }
            }

            //error.Clear();
            //for (int i = 0; i < scriptingSyntaxDatums.Length; i++)
            //{
            //    ScriptingSyntaxDatum datum = scriptingSyntaxDatums[i];
            //    if (datum.Previous == null && (ushort)datum.Flags != 0xBABA && datum.OwnerFunction != BuiltInFunctions.Begin)
            //    {
            //        error.Add(datum);
            //    }
            //}

            return scriptingSyntaxDatums;
        }

        public static void Write(MapStream map, int scnrOffset, ScriptingSyntaxDatum[] scriptingSyntaxDatums)
        {
            map.ReadReflexiveAt(scnrOffset + Offset);
            for (int i = 0; i < scriptingSyntaxDatums.Length; i++)
            {
                ScriptingSyntaxDatum ssd = scriptingSyntaxDatums[i];
                map.Write(ssd.ConstantType);
                map.Write((ushort)ssd.OwnerFunction);
                map.Write((ushort)ssd.Type);
                map.Write((ushort)ssd.Flags);
                map.Write(ssd.SiblingIndex);
                map.Write(ssd.SiblingIndexSalt);
                map.Write(ssd.Pointer);
                map.Write(ssd.Data);
            }
        }
    }
}
